iT邦幫忙

2022 iThome 鐵人賽

1
Modern Web

就是要搞懂 JavaScript 啦!系列 第 72

Day72 Async:async 與 await

  • 分享至 

  • xImage
  •  

async/await 是繼 PromiseGenerator 之後,JS 對異步提出的新解決方案。與 Generator 設置標記並暫停跳出的概念不同,async/await 所做的是「等待」異步直到完成,也就是達成了對程式的「阻塞(blocking)」,等到獲得異步結果後才繼續接下來的動作。

如果說最基礎的 Promise 物件是在送出異步後,再手動將 Promise 展開以確認解析結果,那 async/await 就是送出異步後站在原地等待,等到解析完畢後由程式自動打開 Promise 的結果送到你眼前。

以下就來看看 async/await 的使用方法。

async

async 函式的本質是 Promise 的語法糖,因此 async 函式調用後返回的也是一個 Promise 物件。如果 async 函式執行成功,返回的便是成功並帶有 async 回傳值的 Promise ;如果 async 函式收到錯誤或拒絕,則會返回拒絕狀態的 Promise

async function async1() {
  return 1
}
const resolvedPromise = async1();
resolvedPromise.then(val => console.log(val));
// 1

async function async2() {
  foo();
}
const rejectedPromise = async2();
rejectedPromise.catch(err => console.log(err));
// ReferenceError: foo is not defined

await

await 則是(只)能夠在 async 函式內使用的關鍵字,當程式遇到 await 時,會暫停函式的執行,等待傳遞給 await 的內容解析完畢返回時才繼續執行。如果傳遞給 await 的表達式是一個 Promise,就會將 Promise 展開後返回解析獲得的結果:

const p = new Promise(resolve => {
  setTimeout(() => {
    resolve(10);
  }, 2000);
});

async function asyncFunc() {
  var rv = await p; // 等待 2 秒取得 resolved value
  console.log(rv); // 10
  return rv * 2;
}

asyncFunc().then(val => {
  console.log(val); // 20
});

在沒使用 await 時,程式不會暫停等待異步,而是持續執行直到完畢:

// 使用 IIFE 直接調用
(async function () {
  let i = 0;
  new Promise((resolve, reject) => {
    setTimeout(() => {
      i++;
      resolve();
    }, 0);
  });
  console.log(i); // 0
})();

await 則可以讓程式停下等待異步,直到返回結果後才繼續執行:

// 使用 IIFE 直接調用
(async function () {
  let i = 0;
  // 程式暫停等待 Promise 解析完畢
  await new Promise((resolve, reject) => {
    setTimeout(() => {
      i++;
      resolve();
    }, 0);
  });
  
  // 異步解析完畢後才往下執行
  console.log(i); // 1
})();

另外 await 使用的位置,對等待順序來說會有著微妙的差別:

function resolveAfter2Seconds(x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x);
    }, 2000);
  });
}

async function add1(x) {
  const a = await resolveAfter2Seconds("o");
  const b = await resolveAfter2Seconds("o");
  return x + a + b;
}

add1("f").then(v => {
  console.log(v); // prints "foo" after 4 seconds.
});

async function add2(x) {
  const p_a = resolveAfter2Seconds("a");
  const p_b = resolveAfter2Seconds("r");
  return x + await p_a + await p_b;
}

add2("b").then(v => {
  console.log(v); // prints "bar" after 2 seconds.
});

上面的 add1 等完 a 之後才去排隊 b,總共耗時 4 秒,add2 則是一次等待兩個 Promise,總共耗時 2 秒。

await 如果收到非 Promise,則會將其視為已解析的 Promise

(async function f2() {
  var y = await 20;
  console.log(y); // 20
})();

Promise 所屬的方法當然也可以使用:

async function foo(x, y, z) {
  const p1 = await Promise.all(x);
  const p2 = await Promise.race(y);
  const p3 = await Promise.resolve(z);
  return [p1, p2, p3];
}

foo([1, 2, 3], ["A", "B"], true).then(v => {
  const [p1, p2, p3] = v;
  console.log(p1); // [ 1, 2, 3 ]
  console.log(p2); // "A"
  console.log(p3); // true
});

錯誤處理

如果 Promise 失敗或被拒絕,async 會丟出 Error 並返回 reject() 收到的訊息:

async function foo() {
  try {
    var p = await Promise.reject("rejected");
  } catch (err) {
    console.log(err); // "rejected"
  }
}
foo();

由於 async 返回的是一個 Promise,因此也可以用 Promise 的拒絕處理流程來捕捉報錯:

async function foo() {
  var p = await Promise.reject("rejected");
}
foo().catch(err => {
  console.log(err); // "rejected"
});

參考資料


上一篇
Day71 Async:Generator
下一篇
Day73 完賽結語
系列文
就是要搞懂 JavaScript 啦!73
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言